/*
 * OREKIT-X
 * Copyright 2002-2008 CS Communication & Systemes
 * 
 * Parts of this software package have been licensed to CS
 * Communication & Systemes (CS) under one or more contributor license
 * agreements.  See the NOTICE file distributed with this work for
 * additional information.
 *  
 * This is an experimental copy of OREKIT from www.orekit.org.
 * Please use the original OREKIT from orekit.org for normal work
 * unrelated to this research project.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You
 * may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 */
package ore.propagation.events;

import ore.SpacecraftState;
import ore.errors.OrekitException;
import ore.propagation.sampling.OrekitStepInterpolator;
import ore.time.AbsoluteDate;

import org.apache.commons.math.ConvergenceException;
import org.apache.commons.math.FunctionEvaluationException;
import org.apache.commons.math.analysis.UnivariateRealFunction;
import org.apache.commons.math.analysis.solvers.BrentSolver;
import org.apache.commons.math.analysis.solvers.UnivariateRealSolver;

/** 
 * This class handles the state for one {@link EventDetector event
 * detector} during integration steps.
 *
 * This class is heavily based on the class with the same name from
 * the Apache commons-math library. The changes performed consist in
 * replacing raw types (double and double arrays) with space dynamics
 * types ({@link AbsoluteDate}, {@link SpacecraftState}).</p> <p>Each
 * time the propagator proposes a step, the event detector should be
 * checked. This class handles the state of one detector during one
 * propagation step, with references to the state at the end of the
 * preceding step. This information is used to determine if the
 * detector should trigger an event or not during the proposed step
 * (and hence the step should be reduced to ensure the event occurs at
 * a bound rather than inside the step).
 */
class EventState 
    extends Object
    implements java.io.Serializable
{
    /** 
     * Event detector.
     */
    private EventDetector detector;
    /** 
     * Time at the beginning of the step.
     */
    private AbsoluteDate t0;
    /** 
     * Value of the event detector at the beginning of the step.
     */
    private double g0;
    /** 
     * Simulated sign of g0 (we cheat when crossing events).
     */
    private boolean g0Positive;
    /** 
     * Indicator of event expected during the step.
     */
    private boolean pendingEvent;
    /** 
     * Occurrence time of the pending event.
     */
    private AbsoluteDate pendingEventTime;
    /** 
     * Occurrence time of the previous event.
     */
    private AbsoluteDate previousEventTime;
    /** 
     * Integration direction.
     */
    private boolean forward;
    /** 
     * Variation direction around pending event, with respect to the
     * integration direction.
     */
    private boolean increasing;
    /** 
     * Next action indicator.
     */
    private int nextAction;


    /** 
     * @param detector monitored event detector
     */
    public EventState(EventDetector detector) {
        super();
        this.detector     = detector;
        this.g0           = Double.NaN;
        this.g0Positive   = true;
        this.increasing   = true;
        this.nextAction   = EventDetector.CONTINUE;
    }


    /** 
     * @return Underlying event detector
     */
    public EventDetector getEventDetector() {
        return this.detector;
    }
    /** 
     * Reinitialize the beginning of the step.
     * 
     * @param state0 State value at the beginning of the step
     * 
     * @exception OrekitException The event detector value cannot be
     * evaluated at the beginning of the step
     */
    public void reinitializeBegin(SpacecraftState state0)
        throws OrekitException
    {
        this.t0 = state0.getDate();
        this.g0 = detector.g(state0);
        this.g0Positive = (this.g0 >= 0);
    }
    /** 
     * Evaluate the impact of the proposed step on the event detector.
     * 
     * @param interpolator Step interpolator for the proposed step
     * 
     * @return true If the event detector triggers an event before
     * the end of the proposed step (this implies the step should be
     * rejected)
     * 
     * @exception OrekitException The switching function cannot be
     * evaluated
     * @exception ConvergenceException An event cannot be located
     */
    public boolean evaluateStep(final OrekitStepInterpolator interpolator)
        throws OrekitException, ConvergenceException
    {
        try {
            this.forward = interpolator.isForward();
            final AbsoluteDate t1 = interpolator.getCurrentDate();
            final double dt = t1.durationFrom(t0);
            final int    n  = Math.max(1, (int) Math.ceil(Math.abs(dt) / detector.getMaxCheckInterval()));
            final double h  = (dt / n);

            AbsoluteDate ta = t0;
            double ga = g0;
            final AbsoluteDate start = ((t1.compareTo(t0) > 0) ?
                                        (new AbsoluteDate(t0,  detector.getThreshold())) :
                                        (new AbsoluteDate(t0, -detector.getThreshold())));
            for (int i = 0; i < n; ++i) {
                /*
                 * Evaluate detector value at the end of the substep
                 */
                final AbsoluteDate tb = new AbsoluteDate(start, (i + 1) * h);
                interpolator.setInterpolatedDate(tb);
                final double gb = detector.g(interpolator.getInterpolatedState());
                /*
                 * Check events occurrence
                 */
                if (g0Positive ^ (gb >= 0)) {
                    /*
                     * There is a sign change: an event is expected during this step
                     */
                    /*
                     * Variation direction, with respect to the integration direction
                     */
                    increasing = (gb >= ga);

                    final UnivariateRealSolver solver = new BrentSolver();
                    solver.setAbsoluteAccuracy(detector.getThreshold());
                    solver.setMaximalIterationCount(detector.getMaxIterationCount());

                    final AbsoluteDate root = new AbsoluteDate(t0, solver.solve(new UnivariateRealFunction() {

                        public double value(final double t) throws FunctionEvaluationException {
                            try {
                                final AbsoluteDate date = new AbsoluteDate(t0, t);
                                interpolator.setInterpolatedDate(date);
                                return detector.g(interpolator.getInterpolatedState());
                            }
                            catch (OrekitException e) {
                                throw new FunctionEvaluationException(e, t);
                            }
                        }
                    }, ta.durationFrom(t0), tb.durationFrom(t0)));

                    if ((previousEventTime == null) ||
                        (Math.abs(previousEventTime.durationFrom(root)) > detector.getThreshold()))
                    {
                        pendingEventTime = root;
                        if (pendingEvent && (Math.abs(t1.durationFrom(pendingEventTime)) <= detector.getThreshold())) {
                            /*
                             * We were waiting for this event which
                             * was found during a previous call for a
                             * step that was rejected, this step must
                             * now be accepted since it properly ends
                             * exactly at the event occurrence
                             */
                            return false;
                        }
                        else {
                            /*
                             * Either we were not waiting for the event or it has
                             * moved in such a way the step cannot be accepted
                             */
                            this.pendingEvent = true;
                            return true;
                        }
                    }
                }
                else {
                    /*
                     * No sign change: there is no event for now
                     */
                    ta = tb;
                    ga = gb;
                }
            }
            /*
             * No event during the whole step
             */
            this.pendingEvent     = false;
            this.pendingEventTime = null;
            return false;

        }
        catch (FunctionEvaluationException e) {
            Throwable cause = e.getCause();
            if (cause instanceof OrekitException)
                throw (OrekitException) cause;
            else
                throw new OrekitException(e.getMessage(), e);
        }
    }
    /** 
     * @return Occurrence time of the event triggered in the current
     * step.
     */
    public AbsoluteDate getEventTime() {
        return this.pendingEventTime;
    }
    /** 
     * Acknowledge the step accepted by the propagator.
     * 
     * @param state Value of the state vector at the end of the step
     * 
     * @exception OrekitException if the value of the switching
     * function cannot be evaluated
     */
    public void stepAccepted(SpacecraftState state)
        throws OrekitException
    {
        this.t0 = state.getDate();
        this.g0 = detector.g(state);

        if (this.pendingEvent) {
            /*
             * Force the sign to its value "just after the event"
             */
            this.previousEventTime = state.getDate();
            this.g0Positive        = this.increasing;
            this.nextAction        = this.detector.eventOccurred(state, !(this.increasing ^ this.forward));
        }
        else {
            this.g0Positive = (this.g0 >= 0);
            this.nextAction = EventDetector.CONTINUE;
        }
    }
    /** 
     * Check if the propagation should be stopped at the end of the
     * current step.
     * @return Propagation should be stopped
     */
    public boolean stop() {
        return (this.nextAction == EventDetector.STOP);
    }
    /** 
     * Let the event detector reset the state if it wants.
     * 
     * @param state Value of the state vector at the beginning of the next step
     * 
     * @return New state (state, when reset is not needed)
     * 
     * @exception OrekitException The state cannot be reset by the
     * event detector
     */
    public SpacecraftState reset(SpacecraftState state)
        throws OrekitException
    {
        if (!this.pendingEvent)
            return state;
        else {
            this.pendingEvent      = false;
            this.pendingEventTime  = null;

            if (this.nextAction == EventDetector.RESET_STATE)
                return this.detector.resetState(state);
            else
                return state;
        }
    }
}
